﻿using System.Collections;
using Memo.Electricity.Setting;
using System.Collections.Generic;
using System;
using UnityEngine;
using Memo.Electricity.Engine;

namespace Memo.Electricity.Game
{
    [RequireComponent(typeof(BoxCollider2D), typeof(Rigidbody2D))]
    public class PlayerController : MonoBehaviour
    {
        #region 内部类型定义
        struct PlayerRaycastOrigins//射线起始点
        {
            public Vector3 topLeft;
            public Vector3 bottomRight;
            public Vector3 bottomLeft;
        }
		public class PlayerCollisionState2D//碰撞检测状态
		{
			public bool right;
			public bool left;
			public bool above;
			public bool below;

			public bool hasCollision()
			{
				return below || right || left || above;
			}

			public void reset()
			{
				right = left = above = below = false;
			}
		}
		#endregion
		public event Action<RaycastHit2D> OnPlayerCollidedEvent;
		#region 公共属性
		public bool isPicking = false;
		[Range(2, 20)]
		public int totalHorizontalRays = 8;
		[Range(2, 20)]
		public int totalVerticalRays = 4;
		[SerializeField]
		public LayerMask platformMask = 0;	
		[HideInInspector]
		public bool keyCodeInput = false;
		[NonSerialized]
		public new Transform transform;
		[NonSerialized]
		public BoxCollider2D boxCollider;
		[NonSerialized]
		public PlayerCollisionState2D collisionState = new PlayerCollisionState2D();
		[NonSerialized]
		public Vector3 velocity;

		[SerializeField]
		[Range(0.001f, 0.3f)]
		private float _skinWidth = 0.02f;//碰撞体的检测边界
		private GameObject pickedObject;
		private PlayerSetting setting;
		private const float kSkinWidthFloatFudgeFactor = 0.001f;
		private float _verticalDistanceBetweenRays;//垂直方向上射线之间的间隔
		private float _horizontalDistanceBetweenRays;
		private PlayerRaycastOrigins _raycastOrigins;
		private RaycastHit2D[] _raycastHits;
		List<RaycastHit2D> _raycastHitsThisFrame = new List<RaycastHit2D>(2);

		public bool IsGrounded { get => collisionState.below; }
		public float SkinWidth
		{
			get { return _skinWidth; }
			set
			{
				_skinWidth = value;
				RecalculateDistanceBetweenRays();//更新计算射线之间的间隔
			}
		}
        #endregion
        private void Awake()
        {
			transform = GetComponent<Transform>();
			boxCollider = GetComponent<BoxCollider2D>();
			setting = Resources.Load<PlayerSetting>("GameSetting/PlayerSetting");
			SkinWidth = _skinWidth;
		}

		public void RecalculateDistanceBetweenRays()
		{
			// figure out the distance between our rays in both directions
			// horizontal
			var colliderUseableHeight = boxCollider.size.y * Mathf.Abs(transform.localScale.y) - (2f * _skinWidth);//人物的碰撞高度（不包括包围盒）
			_verticalDistanceBetweenRays = colliderUseableHeight / (totalHorizontalRays - 1);//射线之间的距离（包括上下边界）

			// vertical
			var colliderUseableWidth = boxCollider.size.x * Mathf.Abs(transform.localScale.x) - (2f * _skinWidth);
			_horizontalDistanceBetweenRays = colliderUseableWidth / (totalVerticalRays - 1);
		}
		void DrawRay(Vector3 start, Vector3 dir, Color color)
		{
			Debug.DrawRay(start, dir, color);
		}
		public void move(Vector3 deltaMovement)
		{
			// clear our state
			collisionState.reset();
			_raycastHitsThisFrame.Clear();

			PrimeRaycastOrigins();
			if (deltaMovement.x != 0f)
				MoveHorizontally(ref deltaMovement);

			if (deltaMovement.y != 0f)
				MoveVertically(ref deltaMovement);

			deltaMovement.z = 0;
			transform.Translate(deltaMovement, Space.World);

			if (Time.deltaTime > 0f)
				velocity = deltaMovement / Time.deltaTime;//求出每秒钟的运动速度

			// send off the collision events if we have a listener
			if (OnPlayerCollidedEvent != null)
			{
				for (var i = 0; i < _raycastHitsThisFrame.Count; i++)
					OnPlayerCollidedEvent(_raycastHitsThisFrame[i]);
			}
		}
		void PrimeRaycastOrigins()//在包围盒的边界插入射线
		{
			// our raycasts need to be fired from the bounds inset by the skinWidth
			var modifiedBounds = boxCollider.bounds;
			modifiedBounds.Expand(-2f * _skinWidth);
			_raycastOrigins.topLeft = new Vector2(modifiedBounds.min.x, modifiedBounds.max.y);
			_raycastOrigins.bottomRight = new Vector2(modifiedBounds.max.x, modifiedBounds.min.y);
			_raycastOrigins.bottomLeft = modifiedBounds.min;
		}
		void MoveHorizontally(ref Vector3 deltaMovement)
		{

			var isGoingRight = deltaMovement.x > 0;
			var rayDistance = Mathf.Abs(deltaMovement.x) + _skinWidth;//射线距离
			var rayDirection = isGoingRight ? Vector2.right : -Vector2.right;//射线方向
			var initialRayOrigin = isGoingRight ? _raycastOrigins.bottomRight : _raycastOrigins.bottomLeft;//射线起点

			for (var i = 0; i < totalHorizontalRays; i++)
			{
				var ray = new Vector2(initialRayOrigin.x, initialRayOrigin.y + i * _verticalDistanceBetweenRays);

				DrawRay(ray, rayDirection * rayDistance, Color.red);

				_raycastHits = Physics2D.RaycastAll(ray, rayDirection, rayDistance, platformMask);
				if (_raycastHits != null)
				{
					foreach (RaycastHit2D item in _raycastHits)
					{
						if (item.collider.gameObject != gameObject && (item.collider.isTrigger == false || (item.collider.gameObject.CompareTag("Player") && item.collider.gameObject.transform != transform)))//忽略自身碰撞
						{
							// the bottom ray can hit a slope but no other ray can so we have special handling for these cases
							if (i == 0)
							{
								_raycastHitsThisFrame.Add(item);
							}

							// set our new deltaMovement and recalculate the rayDistance taking it into account
							deltaMovement.x = item.point.x - ray.x;
							rayDistance = Mathf.Abs(deltaMovement.x);

							// remember to remove the skinWidth from our deltaMovement
							if (isGoingRight)
							{
								deltaMovement.x -= _skinWidth;
								collisionState.right = true;
							}
							else
							{
								deltaMovement.x += _skinWidth;
								collisionState.left = true;
							}
							_raycastHitsThisFrame.Add(item);

							// we add a small fudge factor for the float operations here. if our rayDistance is smaller
							// than the width + fudge bail out because we have a direct impact
							if (rayDistance < _skinWidth + kSkinWidthFloatFudgeFactor)
								break;
							break;
						}
					}
				}
			}
			if (collisionState.right && collisionState.left)
				deltaMovement.x = 0;
		}
		void MoveVertically(ref Vector3 deltaMovement)
		{
			float vy = deltaMovement.y;
			var isGoingUp = deltaMovement.y > 0;
			var rayDistance = Mathf.Abs(deltaMovement.y) + _skinWidth;
			var rayDirection = isGoingUp ? Vector2.up : -Vector2.up;
			var initialRayOrigin = isGoingUp ? _raycastOrigins.topLeft : _raycastOrigins.bottomLeft;

			// apply our horizontal deltaMovement here so that we do our raycast from the actual position we would be in if we had moved
			initialRayOrigin.x += deltaMovement.x;

			// if we are moving up, we should ignore the layers in oneWayPlatformMask
			for (var i = 0; i < totalVerticalRays; i++)
			{
				var ray = new Vector2(initialRayOrigin.x + i * _horizontalDistanceBetweenRays, initialRayOrigin.y);

				if (rayDirection.y < 0 && vy < 0)
				{
					rayDistance += 0.01f;
				}

				DrawRay(ray, rayDirection * rayDistance, Color.red);
				
				_raycastHits = Physics2D.RaycastAll(ray, rayDirection, rayDistance, platformMask);
				if (_raycastHits != null)
				{
					foreach (RaycastHit2D item in _raycastHits)
					{
						if (item.collider.gameObject != gameObject)//忽略自身碰撞
						{
							if (item.collider.isTrigger == false || (item.collider.gameObject.CompareTag("Player") && item.collider.gameObject.transform != transform))
							{
								deltaMovement.y = item.point.y - ray.y;
								rayDistance = Mathf.Abs(deltaMovement.y);

								// remember to remove the skinWidth from our deltaMovement
								if (isGoingUp)
								{
									deltaMovement.y -= _skinWidth;
									collisionState.above = true;
								}
								else
								{
									deltaMovement.y += _skinWidth;
									collisionState.below = true;
								}

								_raycastHitsThisFrame.Add(item);

								if (rayDistance < _skinWidth + kSkinWidthFloatFudgeFactor)
									break;
								break;
							}
						}
					}
				}
				if (IsGrounded)
				{
					ray = new Vector2(_raycastOrigins.topLeft.x + i * _horizontalDistanceBetweenRays, _raycastOrigins.topLeft.y);
					_raycastHits = Physics2D.RaycastAll(ray, Vector2.up, rayDistance, platformMask);
					if (_raycastHits != null)
					{
						foreach (RaycastHit2D item in _raycastHits)
						{
							if (item.collider.gameObject != gameObject)//忽略自身碰撞
							{
								if (item.collider.isTrigger == false)
								{
									if (item.collider.gameObject.GetComponent<Rigidbody2D>())
									{
										float dis = Mathf.Abs(item.point.y - ray.y);
										if (dis <= _skinWidth)
										{
											collisionState.above = true;
											item.collider.gameObject.GetComponent<Rigidbody2D>().velocity = Vector3.zero;
											item.collider.gameObject.transform.position = new Vector3(item.collider.gameObject.transform.position.x, ray.y + _skinWidth + item.collider.gameObject.GetComponent<BoxCollider2D>().bounds.extents.y, 0);
										}
									}
								}
							}
						}
					}
				}
			}
		}
		public void PickUpRelay()
		{
			var isGoingRight = transform.localScale.x > 0;
			var rayDistance = setting.PickDistance + _skinWidth;//射线距离
			var rayDirection = isGoingRight ? Vector2.right : -Vector2.right;//射线方向
			var initialRayOrigin = isGoingRight ? _raycastOrigins.bottomRight : _raycastOrigins.bottomLeft;//射线起点
			var ray = new Vector2(initialRayOrigin.x, initialRayOrigin.y + 2 * _verticalDistanceBetweenRays);//射线的位置还需要修改
			DrawRay(ray, rayDirection * rayDistance, Color.red);
			_raycastHits = Physics2D.RaycastAll(ray, rayDirection, rayDistance, platformMask);
			if (_raycastHits != null)
			{
				foreach (RaycastHit2D item in _raycastHits)
				{
					if (item.collider.gameObject != gameObject && item.collider.gameObject.GetComponent<CRelay>() != null && item.collider.gameObject.GetComponent<CRelay>().isPicked == false && item.collider.gameObject.GetComponent<CRelay>().Pickable == true)
					{
						isPicking = true;
						pickedObject = item.collider.gameObject;
						pickedObject.gameObject.GetComponent<CRelay>().isPicked = true;
						pickedObject.GetComponent<Rigidbody2D>().gravityScale = 0;
						pickedObject.GetComponent<BoxCollider2D>().isTrigger = true;
						RefreshRelayPostion();
						break;
					}
				}
			}
		}
		public void PutDownRelay()
		{
			if (CanPutDown())
			{
				isPicking = false;
				pickedObject.gameObject.GetComponent<CRelay>().isPicked = false;
				pickedObject.GetComponent<Rigidbody2D>().gravityScale = 1;
				pickedObject.GetComponent<BoxCollider2D>().isTrigger = false;
			}
		}
		public void RefreshRelayPostion()
		{
			int isGoingRight = transform.localScale.x > 0 ? 1 : -1;
			pickedObject.transform.position = Vector3.Lerp(pickedObject.transform.position, new Vector3(transform.position.x + setting.RelayLocalPos.x * isGoingRight, transform.position.y + setting.RelayLocalPos.y, transform.position.z + setting.RelayLocalPos.z), (float)(1 - Math.Pow(setting.PickLambda, setting.PickMiu * Time.deltaTime)));
			//第三个参数为1-λ^μΔt
		}

		private bool CanPutDown()
		{
			if (pickedObject.GetComponent<BoxCollider2D>().IsTouchingLayers(platformMask))
			{
				CAudioMgr.Inst.PlayOneShot("Audios/error");
				//发出警告的声音
				return false;
			}
			return true;
		}
	}
}

